package cn.zmx.ipregion;

import lombok.Data;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Service;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

@Service
public class IpRegionQueryImpl implements IpRegionQuery,InitializingBean{

    private final String XML_IP_START = "start";
    private final String XML_IP_END = "end";
    private final String XML_IP_PROVINCE = "province";
    private final String XML_IP_CITY = "city";
    private final String XML_IP_DES = "des";
    private IpRegionNode rootNode = new IpRegionNode();

    @Override
    public IpRegion findIp(String ip) {
        IpRegion ipRegion = new IpRegion();
        IpRegionNode curNode = rootNode;
        int ip_int = ipToInt(ip);
        if (ip_int == -1)
            return null;

        for (int i = 0; i < 32; i++) {
            int ip_s_bit = (0x80000000 & ip_int) >>> 31;
            if (ip_s_bit == 0)
                curNode = curNode.getLeftNode();
            else
                curNode = curNode.getRightNode();
            if (curNode == null) {
                return null;
            }
            if (curNode.getAddressCode() != null && !curNode.getAddressCode().trim().equals("")) {
                String addressCode = curNode.getAddressCode();
                String[] split = addressCode.split("-");
                ipRegion.setProvince(split[0]);
                ipRegion.setCity(split[1]);
                ipRegion.setDes(split[2]);
                return ipRegion;
            }
            ip_int = ip_int << 1;
        }
        return null;
    }

    private void buildTrain() throws DocumentException, IOException {
        List<IpRegionRelation> ipRelationList;
        ipRelationList = getIpDatabse();
        int count = 0;
        for (IpRegionRelation ipRelation : ipRelationList) {
            train(ipRelation.getIpStart(), ipRelation.getIpEnd(), ipRelation.getProvince(), ipRelation.getCity(), ipRelation.getDes());
            if (count > 10) {
                break;
            }
        }
    }

    private List<IpRegionRelation> getIpDatabse() throws DocumentException, IOException {
        SAXReader reader = new SAXReader();

        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("ipDatabase.xml");

        Document doc
                = reader.read(resourceAsStream);
        Element root = doc.getRootElement();

        List<Element> list = root.elements();
        List<IpRegionRelation> empList = new ArrayList<IpRegionRelation>();

        for (Element element : list) {
            IpRegionRelation ipRelation = new IpRegionRelation();
            Element start = element.element(XML_IP_START);
            ipRelation.setIpStart(start.getText());
            Element end = element.element(XML_IP_END);
            ipRelation.setIpEnd(end.getText());
            Element Province = element.element(XML_IP_PROVINCE);
            ipRelation.setProvince(Province.getText());
            Element city = element.element(XML_IP_CITY);
            ipRelation.setCity(city.getText());
            Element des = element.element(XML_IP_DES);
            ipRelation.setDes(des.getText());
            empList.add(ipRelation);
        }
        return empList;
    }

    private void train(String ipStart, String ipEnd, String province, String city, String des) {
        int ip_s = ipToInt(ipStart);
        int ip_e = ipToInt(ipEnd);
        String addressCode = province + "-" + city + "-" + des;
        if (ip_e == -1 || ip_s == -1)
            return;

        IpRegionNode curNode = rootNode;
        IpRegionNode leftNode = null;
        IpRegionNode rightNode = null;
        boolean flag = false;

        for (int i = 0; i < 32; i++) {

            int ip_s_bit = (0x80000000 & ip_s) >>> 31;
            int ip_e_bit = (0x80000000 & ip_e) >>> 31;

            if (flag == false) {

                if ((ip_s_bit ^ ip_e_bit) == 0) {

                    if (ip_s_bit == 1) {
                        if (curNode.getRightNode() == null) {
                            curNode.setRightNode(new IpRegionNode());
                        }
                        curNode = curNode.getRightNode();
                    } else {
                        if (curNode.getLeftNode() == null) {
                            curNode.setLeftNode(new IpRegionNode());
                        }
                        curNode = curNode.getLeftNode();
                    }
                    if (i == 31) {
                        curNode.setAddressCode(addressCode);
                    }

                } else {
                    flag = true;
                    if (curNode.getLeftNode() == null) {
                        curNode.setLeftNode(new IpRegionNode());
                    }
                    leftNode = curNode.getLeftNode();

                    if (curNode.getRightNode() == null) {
                        curNode.setRightNode(new IpRegionNode());
                    }

                    rightNode = curNode.getRightNode();

                    if (i == 31) {
                        leftNode.setAddressCode(addressCode);
                        rightNode.setAddressCode(addressCode);
                    }
                }
            } else {
                if (ip_s_bit == 1) {
                    if (leftNode.getRightNode() == null) {
                        leftNode.setRightNode(new IpRegionNode());
                    }
                    leftNode = leftNode.getRightNode();
                } else {
                    if (leftNode.getLeftNode() == null) {
                        leftNode.setLeftNode(new IpRegionNode());
                    }
                    if (leftNode.getRightNode() == null) {
                        leftNode.setRightNode(new IpRegionNode());
                    }
                    leftNode.getRightNode().setAddressCode(addressCode);
                    leftNode = leftNode.getLeftNode();
                }
                if (i == 31)
                    leftNode.setAddressCode(addressCode);

                if (ip_e_bit == 1) {
                    if (rightNode.getRightNode() == null) {
                        rightNode.setRightNode(new IpRegionNode());
                    }
                    if (rightNode.getLeftNode() == null) {
                        rightNode.setLeftNode(new IpRegionNode());
                    }
                    rightNode.getLeftNode().setAddressCode(addressCode);
                    rightNode = rightNode.getRightNode();
                } else {
                    if (rightNode.getLeftNode() == null) {
                        rightNode.setLeftNode(new IpRegionNode());
                    }
                    rightNode = rightNode.getLeftNode();
                }
                if (i == 31)
                    rightNode.setAddressCode(addressCode);
            }

            ip_s = ip_s << 1;
            ip_e = ip_e << 1;
        }
    }

    private int ipToInt(String strIP) {
        try {
            int[] ip = new int[4];
            int position1 = strIP.indexOf(".");
            int position2 = strIP.indexOf(".", position1 + 1);
            int position3 = strIP.indexOf(".", position2 + 1);

            ip[0] = Integer.parseInt(strIP.substring(0, position1));
            ip[1] = Integer.parseInt(strIP.substring(position1 + 1, position2));
            ip[2] = Integer.parseInt(strIP.substring(position2 + 1, position3));
            ip[3] = Integer.parseInt(strIP.substring(position3 + 1));
            int ip_int = (ip[0] << 24) + (ip[1] << 16) + (ip[2] << 8) + ip[3];
            return ip_int;
        } catch (Exception e) {
            return -1;
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        buildTrain();
    }

    @Data
    private class IpRegionNode {
        private IpRegionNode leftNode;

        private IpRegionNode rightNode;

        private String addressCode;
    }

    @Data
    private class IpRegionRelation {

        private String ipStart;

        private String ipEnd;

        private int ipCode;

        private String province;

        private String city;

        private String des;
    }
}
